#ifndef INCLUDE_GUARD_FZW0VXLCRGTBA5M8Q321OS6K47HEN9DJPU
#define INCLUDE_GUARD_FZW0VXLCRGTBA5M8Q321OS6K47HEN9DJPU

/*
 * norx_perm.hpp
 * This file is part of norxx
 *
 * Copyright (C) 2014 - Sebastian Gesemann
 *
 * norxx is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * norxx is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with norxx. If not, see <http://www.gnu.org/licenses/>.
 */

#include <cassert>
#include <cstdint>
#include <climits>

#include "array_ref.hpp"
#include "norx_bits.hpp"

namespace norx {

const unsigned state_words = 16;

template<int W> struct perm_params;

template<> struct perm_params<32> {
	typedef u32 type;
	static const unsigned r0 = 8;
	static const unsigned r1 = 11;
	static const unsigned r2 = 16;
	static const unsigned r3 = 31;
};

template<> struct perm_params<64> {
	typedef u64 type;
	static const unsigned r0 = 8;
	static const unsigned r1 = 19;
	static const unsigned r2 = 40;
	static const unsigned r3 = 63;
};

template<unsigned By, class T, int W = bits<T>::value>
inline T rotr(T x)
{
	return (x >> By) | (x << (W-By));
}

constexpr int offset(int b, int i)
{
	return (b & 0xC) | ((b+i) & 0x3); // rotate column index by i
}

#define NORX_ADD(a,b) ((a) = ((a) ^ (b)) ^ (((a) & (b)) << 1))
#define NORX_ROT(a,b,r) ((a) = rotr<r>((a) ^ (b)))

#define NORX_PAR_ADD(a,b) do { \
		NORX_ADD(s[       a   ],s[       b   ]); \
		NORX_ADD(s[offset(a,1)],s[offset(b,1)]); \
		NORX_ADD(s[offset(a,2)],s[offset(b,2)]); \
		NORX_ADD(s[offset(a,3)],s[offset(b,3)]); \
	} while (0)

#define NORX_PAR_ROT(a,b,r) do { \
		NORX_ROT(s[       a   ],s[       b   ],r); \
		NORX_ROT(s[offset(a,1)],s[offset(b,1)],r); \
		NORX_ROT(s[offset(a,2)],s[offset(b,2)],r); \
		NORX_ROT(s[offset(a,3)],s[offset(b,3)],r); \
	} while (0)

// "parallel G" -- hoping for a high instruction-level parallelism
template<int a0, int b0, int c0, int d0, class T>
void parallel_G(array_ref<T> const state)
{
	assert(state.size() == state_words);
	T* const s = state.base();
	typedef perm_params<bits<T>::value> p;
	NORX_PAR_ADD(a0,b0);
	NORX_PAR_ROT(d0,a0,p::r0);
	NORX_PAR_ADD(c0,d0);
	NORX_PAR_ROT(b0,c0,p::r1);
	NORX_PAR_ADD(a0,b0);
	NORX_PAR_ROT(d0,a0,p::r2);
	NORX_PAR_ADD(c0,d0);
	NORX_PAR_ROT(b0,c0,p::r3);
}

#undef NORX_PAR_ADD
#undef NORX_PAR_ROT
#undef NORX_ADD
#undef NORX_ROT

/// apply round function F[W] R times in-place on a word array
/// It is fine for in and out to alias the same array
template<class T>
void F(int R, array_ref<T> io)
{
	assert(io.size()==state_words);
	for (int r=0; r<R; ++r) {
		parallel_G<0,4, 8,12>(io); // G applied "in parallel" on all columns
		parallel_G<0,5,10,15>(io); // G applied "in parallel" on all diagonals
	}
}

/// apply round function F[W] R times from one byte array to another.
/// It is fine for in and out to alias the same byte array.
template<int W>
void F(const int R, array_ref<const u8> const in, array_ref<u8> const out)
{
	typedef perm_params<W> p;
	typedef typename p::type T;
	assert(in .size()==(W/8)*state_words);
	assert(out.size()==(W/8)*state_words);
	T wa[state_words];
	array_ref<T> const war (wa);
	copy(in,war);
	F(R,war);
	copy(war,out);
}

} // namespace norx

#endif

